///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: MRU.java,v 1.8 2005/06/25 23:12:31 doomdark Exp $
//

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************


using System;
using System.Collections;

namespace SharpDBM.helper
{



///
// *  MRU - Most Recently Used cache policy.
// *
// *  Methods are *not* synchronized, so no concurrent access is allowed.
// *
// * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
// * @version $Id: MRU.java,v 1.8 2005/06/25 23:12:31 doomdark Exp $
//
	public class MRU : CachePolicy
	{
	/// Cached object hashtable
		internal Hashtable _hash = new Hashtable();

//    *
//     * Maximum number of objects in the cache.
//
		internal int _max;

//    *
//     * Beginning of linked-list of cache elements.  First entry is element
//     * which has been used least recently.
//
		internal CacheEntry _first;

//    *
//     * End of linked-list of cache elements.  Last entry is element
//     * which has been used most recently.
//
		internal CacheEntry _last;


//    *
//     * Cache eviction listeners
//
        private ArrayList listeners = new ArrayList();

        public ArrayList Listeners
        {
            get { return listeners; }
            set { listeners = value; }
        }


//    *
//     * Construct an MRU with a given maximum number of objects.
//
		public MRU(int max)
		{
			if (max <= 0)
			{
				throw new ArgumentOutOfRangeException("MRU cache must contain at least one entry");
			}
			_max = max;
		}

        public MRU()
        {
        }

//    *
//     * Place an object in the cache.
//

		public virtual void put(object key, object @value)
		{
			CacheEntry entry = (CacheEntry)_hash[key];
			if (entry != null)
			{
				entry.Value = @value;
				touchEntry(entry);
			}
			else
			{
				if (_hash.Count == _max)
				{
				// purge and recycle entry
					entry = purgeEntry();
					entry.setKey(key);
					entry.Value = @value;
				}
				else
				{
					entry = new CacheEntry(key, @value);
				}
				addEntry(entry);
				_hash[entry.getKey()] = entry;
			}
		}


//    *
//     * Obtain an object in the cache
//
		public virtual object @get(object key)
		{
			CacheEntry entry = (CacheEntry)_hash[key];
			if (entry != null)
			{
				touchEntry(entry);
				return entry.Value;
			}
			else
			{
				return null;
			}
		}


//    *
//     * Remove an object from the cache
//
		public virtual void remove(object key)
		{
			CacheEntry entry = (CacheEntry)_hash[key];
			if (entry != null)
			{
				removeEntry(entry);
				_hash.Remove(entry.getKey());
			}
		}


//    *
//     * Remove all objects from the cache
//
		public virtual void removeAll()
		{
			_hash = new Hashtable();
			_first = null;
			_last = null;
		}


//    *
//     * Enumerate elements' values in the cache
//
        public virtual IEnumerator elements()
		{
			//return new MRUEnumeration(_hash.Elements);
            return (IEnumerator)new MRUEnumeration(_hash.GetEnumerator());
		}

//    *
//     * Add a listener to this cache policy
//     *
//     * @param listener Listener to add to this policy
//
		public virtual void addListener(CachePolicyListener listener)
		{
			if (listener == null)
			{
				throw new ArgumentOutOfRangeException("Cannot add null listener.");
			}
			if (! listeners.Contains(listener))
			{
				listeners.Add(listener);
			}
		}

//    *
//     * Remove a listener from this cache policy
//     *
//     * @param listener Listener to remove from this policy
//
		public virtual void removeListener(CachePolicyListener listener)
		{
			listeners.Remove(listener);
		}

//    *
//     * Add a CacheEntry.  Entry goes at the end of the list.
//
		protected internal virtual void addEntry(CacheEntry entry)
		{
			if (_first == null)
			{
				_first = entry;
				_last = entry;
			}
			else
			{
				_last.setNext(entry);
				entry.setPrevious(_last);
				_last = entry;
			}
		}


//    *
//     * Remove a CacheEntry from linked list
//
		protected internal virtual void removeEntry(CacheEntry entry)
		{
			if (entry == _first)
			{
				_first = entry.getNext();
			}
			if (_last == entry)
			{
				_last = entry.getPrevious();
			}
			CacheEntry previous = entry.getPrevious();
			CacheEntry next = entry.getNext();
			if (previous != null)
			{
				previous.setNext(next);
			}
			if (next != null)
			{
				next.setPrevious(previous);
			}
			entry.setPrevious(null);
			entry.setNext(null);
		}

//    *
//     * Place entry at the end of linked list -- Most Recently Used
//
		protected internal virtual void touchEntry(CacheEntry entry)
		{
			if (_last == entry)
			{
				return;
			}
			removeEntry(entry);
			addEntry(entry);
		}

//    *
//     * Purge least recently used object from the cache
//     *
//     * @return recyclable CacheEntry
//
 
//
		protected internal virtual CacheEntry purgeEntry()
		{
			CacheEntry entry = _first;

		// Notify policy listeners first. if any of them throw an
		// eviction exception, then the internal data structure
		// remains untouched.
			CachePolicyListener listener;
			for (int i=0; i<listeners.Count; i++)
			{
				listener = (CachePolicyListener)listeners[i];
				listener.cacheObjectEvicted(entry.Value);
			}

			removeEntry(entry);
			_hash.Remove(entry.getKey());

			entry.Value = null;
			return entry;
		}

	}

///
// * State information for cache entries.
//
public	 class CacheEntry
	{
		private object _key;
		private object _value;

		private CacheEntry _previous;
		private CacheEntry _next;

		internal CacheEntry(object key, object @value)
		{
			_key = key;
			_value = @value;
		}

		internal virtual object getKey()
		{
			return _key;
		}

		internal virtual void setKey(object obj)
		{
			_key = obj;
		}

		internal virtual object Value
		{
			get
			{
				return _value;
			}
			set
			{
				_value = value;
			}
		}


		internal virtual CacheEntry getPrevious()
		{
			return _previous;
		}

		internal virtual void setPrevious(CacheEntry entry)
		{
			_previous = entry;
		}

		internal virtual CacheEntry getNext()
		{
			return _next;
		}

		internal virtual void setNext(CacheEntry entry)
		{
			_next = entry;
		}
	}

///
// * Enumeration wrapper to return actual user objects instead of
// * CacheEntries.
//
    public class MRUEnumeration : IEnumerator
	{
        internal IEnumerator _enum;

        internal MRUEnumeration(IEnumerator enume)
		{
			_enum = enume;
		}

		public virtual bool hasMoreElements()
		{
			if (_enum.MoveNext())
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		public virtual object nextElement()
		{
			_enum.MoveNext();
			//CacheEntry entry = (CacheEntry)_enum.Current;

            DictionaryEntry entry = (DictionaryEntry)_enum.Current;
            return ((CacheEntry)entry.Value).Value;
		}

        public  object Current()
        {
            
            //CacheEntry entry = (CacheEntry)_enum.Current;
            DictionaryEntry entry = (DictionaryEntry)_enum.Current;
            return ((CacheEntry)entry.Value).Value;
        }

        object IEnumerator.Current
        {

            get { return Current(); }

        }


        
        // implement current, reset and movenext
        public virtual void Reset()
        {
            _enum.Reset();

        }


        public virtual Boolean MoveNext()
        {
            if (_enum.MoveNext())
            {
                return true;
            }
            else
            {
                return false;
            }
        }

	}

}